﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using Bio.Views.Structure.Editor.Internal.Commands;
using Bio.Views.Structure.Editor.Internal.Selection;
using Bio.Views.Structure.Editor.Internal.Viewport;
using Bio.Views.Structure.Editor.ViewModels;
using JulMar.Windows.Mvvm;

namespace Bio.Views.Structure.Editor.Views
{
    /// <summary>
    /// 
    /// </summary>
    public partial class SecondaryStructureEditor : UserControl
    {
        #region Dependency Properties

        public static readonly DependencyProperty ZoomFactorProperty = DependencyProperty.Register("ZoomFactor",
           typeof(double), typeof(SecondaryStructureEditor));

        public double ZoomFactor
        {
            get { return (double)GetValue(ZoomFactorProperty); }
            set { SetValue(ZoomFactorProperty, value); }
        }

        public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register("SelectionMode",
            typeof(StructureElementSelectionMode), typeof(SecondaryStructureEditor), new FrameworkPropertyMetadata(StructureElementSelectionMode.None));

        public StructureElementSelectionMode SelectionMode
        {
            get { return (StructureElementSelectionMode)GetValue(SelectionModeProperty); }
            set { SetValue(SelectionModeProperty, value); }
        }

        public static readonly DependencyProperty SelectedStructureElementIDProperty = DependencyProperty.Register("SelectedStructureElementID",
            typeof(int), typeof(SecondaryStructureEditor), new FrameworkPropertyMetadata(-1, OnSelectedStructureElementIDChanged));

        public int SelectedStructureElementID
        {
            get { return (int)GetValue(SelectedStructureElementIDProperty); }
            set { SetValue(SelectedStructureElementIDProperty, value); }
        }

        private static void OnSelectedStructureElementIDChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            /*SecondaryStructureEditor editor = d as SecondaryStructureEditor;
            if (editor != null)
            {
               //If the event was triggered inside the drawing, then e.NewValue should match that set on the drawing itself;
                int newSelectedElementID = (int)e.NewValue;
                if (newSelectedElementID == editor.secondaryStructureDrawing.SelectedStructureElementVisualID)
                {
                    
                }
                else
                {
                    //In here, the event was triggered outside the drawing and we might want to update the viewport.
                    editor.secondaryStructureDrawing.SelectedStructureElementVisualID = newSelectedElementID;
                }
                //editor.secondaryStructureDrawing.SelectedStructureElementVisualID = editor.SelectedStructureElementID; //This will trigger the routed event from the drawing
            }*/
        }

        public static readonly DependencyProperty ViewportTopLeftProperty = DependencyProperty.Register("ViewportTopLeft",
            typeof(Point), typeof(SecondaryStructureEditor), new FrameworkPropertyMetadata(OnViewportTopLeftChanged));

        public Point ViewportTopLeft
        {
            get { return (Point)GetValue(ViewportTopLeftProperty); }
            set { SetValue(ViewportTopLeftProperty, value); }
        }

        private static void OnViewportTopLeftChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureEditor editor = d as SecondaryStructureEditor;
            if (editor != null)
            {
                Point newTopLeft = (Point)e.NewValue;
                Point oldTopLeft = (Point)e.OldValue;

                if (newTopLeft.Equals(oldTopLeft))
                    return;

                if (newTopLeft.Equals(editor.ViewportManager.ViewportBounds.TopLeft))
                    return;

                editor.ViewportManager.ScrollViewportTo(newTopLeft);     
            }
        }

        /*public static readonly DependencyProperty ViewportLeftProperty = DependencyProperty.Register("ViewportLeft",
            typeof(double), typeof(SecondaryStructureEditor), new FrameworkPropertyMetadata(OnViewportLeftChanged));

        public double ViewportLeft
        {
            get { return (double)GetValue(ViewportLeftProperty); }
            set { SetValue(ViewportLeftProperty, value); }
        }

        private static void OnViewportLeftChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureEditor editor = d as SecondaryStructureEditor;
            if (editor != null)
            {
                double newLeft = (double)e.NewValue;
                double oldLeft = (double)e.OldValue;

                if (newLeft == oldLeft)
                    return;

                if (newLeft == editor.ViewportManager.ViewportLeft)
                    return;
                
                if(editor.ZoomFactor != 0)
                    editor.ViewportManager.ScrollViewportTo(new Point(newLeft * editor.ZoomFactor, editor.ViewportManager.ViewportTop * editor.ZoomFactor));
                else
                    editor.ViewportManager.ScrollViewportTo(new Point(newLeft, editor.ViewportManager.ViewportTop));
            }
        }

        public static readonly DependencyProperty ViewportTopProperty = DependencyProperty.Register("ViewportTop",
            typeof(double), typeof(SecondaryStructureEditor), new FrameworkPropertyMetadata(OnViewportTopChanged));

        public double ViewportTop
        {
            get { return (double)GetValue(ViewportTopProperty); }
            set { SetValue(ViewportTopProperty, value); }
        }

        private static void OnViewportTopChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureEditor editor = d as SecondaryStructureEditor;
            if (editor != null)
            {
                double newTop = (double)e.NewValue;
                double oldTop = (double)e.OldValue;

                if (newTop == oldTop)
                    return;

                if (newTop == editor.ViewportManager.ViewportTop)
                    return;

                if(editor.ZoomFactor != 0)
                    editor.ViewportManager.ScrollViewportTo(new Point(editor.ViewportManager.ViewportLeft * editor.ZoomFactor, newTop * editor.ZoomFactor));
                else
                    editor.ViewportManager.ScrollViewportTo(new Point(editor.ViewportManager.ViewportLeft, newTop));
            }
        }*/

        private static readonly DependencyPropertyKey ViewportSizePropertyKey = DependencyProperty.RegisterReadOnly("ViewportSize",
            typeof(Size), typeof(SecondaryStructureEditor), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty ViewportSizeProperty = ViewportSizePropertyKey.DependencyProperty;

        public Size ViewportSize
        {
            get { return (Size)GetValue(ViewportSizeProperty); }
            private set { SetValue(ViewportSizePropertyKey, value); }
        }

        #endregion

        public ICommand PrintCommand { get; private set; }

        public SecondaryStructureEditor()
        {
            InitializeComponent();
            InitializeZoomFactors();
            InitializeSelectionModes();
            ViewportManager = new DrawingViewportManager(drawingScroller, ZoomFactor, Size.Empty);
            ViewportManager.ViewportSizeChangedEvent += new EventHandler(OnViewportManagerViewportSizeChanged);
            ViewportManager.ViewportLocationChangedEvent += new EventHandler(OnViewportManagerViewportLocationChanged);
            SizeChanged += new SizeChangedEventHandler(OnSecondaryStructureEditorSizeChanged);
            zoomSelector.ItemsSource = ZoomFactors;
            zoomSelector.SelectedIndex = 4;
            Loaded += new RoutedEventHandler(OnSecondaryStructureEditorLoaded);
            DataContextChanged += new DependencyPropertyChangedEventHandler(OnSecondaryStructureEditorDataContextChanged);
            secondaryStructureDrawing.SelectedStructureElementVisualIDChanged += new RoutedEventHandler(OnSecondaryStructureDrawingSelectedStructureElementVisualIDChanged);
            secondaryStructureDrawing.StructureDataChanged += new RoutedEventHandler(OnSecondaryStructureDrawingStructureDataChanged);
            secondaryStructureDrawing.SizeChanged += new SizeChangedEventHandler(OnSecondaryStructureDrawingSizeChanged);

            //PrintCommand = new DelegatingCommand(OnSecondaryStructureDrawingPrint);

            CommandBindings.Add(new CommandBinding(StructureEditorCommands.ChangeNucleotideFill, StructureEditorCommands.OnChangeNucleotideFill, StructureEditorCommands.OnCanEditVisualComponentBrush));
            CommandBindings.Add(new CommandBinding(StructureEditorCommands.ChangeNucleotideCircleFill, StructureEditorCommands.OnChangeNucleotideCircleFill, StructureEditorCommands.OnCanEditVisualComponentBrush));
            CommandBindings.Add(new CommandBinding(StructureEditorCommands.ChangeNucleotideCircleStroke, StructureEditorCommands.OnChangeNucleotideCircleStroke, StructureEditorCommands.OnCanEditVisualComponentBrush));
            CommandBindings.Add(new CommandBinding(StructureEditorCommands.ChangeBasePairConnectorFill, StructureEditorCommands.OnChangeBasePairConnectorFill, StructureEditorCommands.OnCanEditVisualComponentBrush));
            CommandBindings.Add(new CommandBinding(StructureEditorCommands.ChangeBasePairConnectorStroke, StructureEditorCommands.OnChangeBasePairConnectorStroke, StructureEditorCommands.OnCanEditVisualComponentBrush));
        }      

        protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (drawingScroller.IsMouseOver && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
            {
                if (ViewportSize.Height < drawingScroller.ExtentHeight ||
                    ViewportSize.Width < drawingScroller.ExtentWidth)
                {
                    InPanMode = true;
                    LastPanPoint = e.GetPosition(this);
                    Cursor = Cursors.ScrollAll;
                }
                e.Handled = true;
            }
            base.OnPreviewMouseLeftButtonDown(e);
        }

        protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (InPanMode)
            {
                InPanMode = false;
                Cursor = Cursors.Arrow;
                e.Handled = true;
            }
            base.OnPreviewMouseLeftButtonUp(e);
        }

        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            if (InPanMode)
            {
                Point currentPosition = e.GetPosition(this);
                Point delta = new Point(LastPanPoint.X - currentPosition.X,
                    LastPanPoint.Y - currentPosition.Y);
                ViewportManager.ScrollViewportBy(delta);
                LastPanPoint = currentPosition;
                e.Handled = true;
            }
            
            if (secondaryStructureDrawing.CurrentStructureModelEditingTool != null)
            {
                if (secondaryStructureDrawing.CurrentStructureModelEditingTool.IsActive)
                {
                    if (secondaryStructureDrawing.IsMouseOver)
                    {
                        structureModelEditingToolMessageBox.ClearValue(Popup.IsOpenProperty);
                        structureModelEditingToolMessageBox.IsOpen = true;
                    }
                    else
                    {
                        structureModelEditingToolMessageBox.IsOpen = false;
                    }
                }
                else
                {
                    if(structureModelEditingToolMessageBox.IsOpen)
                        structureModelEditingToolMessageBox.IsOpen = false;
                }
            }

            base.OnPreviewMouseMove(e);     
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            if (InPanMode)
            {
                InPanMode = false;
                Cursor = Cursors.Arrow;
                e.Handled = true;
            }
            base.OnMouseLeave(e);
        }

        private List<SelectionModeData> SelectionModes { get; set; }
        private List<ZoomFactorData> ZoomFactors { get; set; }
        private DrawingViewportManager ViewportManager { get; set; }
        private bool InPanMode { get; set; }
        private Point LastPanPoint { get; set; }
        private SecondaryStructureEditorViewModel ViewModel { get; set; }

        private void OnSecondaryStructureDrawingPrint()
        {
            PrintDialog printDialog = new PrintDialog();
            if (printDialog.ShowDialog() == true)
            {
                printDialog.PrintTicket.TrueTypeFontMode = TrueTypeFontMode.DownloadAsNativeTrueTypeFont;
                printDialog.PrintVisual(secondaryStructureDrawing, ViewModel.Title);
            }
        }

        private void OnSecondaryStructureEditorLoaded(object sender, RoutedEventArgs e)
        {
            //ViewportManager.DrawingSize = new Size(secondaryStructureDrawing.DrawingWidth, secondaryStructureDrawing.DrawingHeight);
            ViewportManager.DrawingSize = new Size(secondaryStructureDrawing.CanvasWidth, secondaryStructureDrawing.CanvasHeight);
        }

        private void OnSecondaryStructureDrawingStructureDataChanged(object sender, RoutedEventArgs e)
        {
            //ViewportManager.DrawingSize = new Size(secondaryStructureDrawing.DrawingWidth, secondaryStructureDrawing.DrawingHeight);
            ViewportManager.DrawingSize = new Size(secondaryStructureDrawing.CanvasWidth, secondaryStructureDrawing.CanvasHeight);
            if (ViewModel != null)
            {
                ViewModel.CurrentCanvasSize = ViewportManager.DrawingSize;
                ViewModel.CurrentDrawingSize = new Size(secondaryStructureDrawing.DrawingWidth, secondaryStructureDrawing.DrawingHeight);
                ViewModel.CurrentDrawingOffset = secondaryStructureDrawing.DrawingOffset;
                ViewModel.CurrentDrawingSnapshot = secondaryStructureDrawing.DrawingSnapshot;

                //Kluge
                /*MenuItem printCommandItem = ViewModel.ActionsMenu.Where(mi => mi.Text!=null && mi.Text.Equals("Print")).FirstOrDefault();
                if(printCommandItem==null)
                    ViewModel.ActionsMenu.Add(new MenuItem("Print") { Command = PrintCommand });*/
            }
            selectionModeSelector.SelectedIndex = 0; //Reset to StructureElementSelectionMode.None?
            ResetZoomFactor();
        }

        private void OnSecondaryStructureEditorDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            ViewModel = DataContext as SecondaryStructureEditorViewModel;
            BindingOperations.SetBinding(this, SelectionModeProperty, new Binding("ElementSelectionMode") { Mode = BindingMode.OneWayToSource });
            BindingOperations.SetBinding(this, SelectedStructureElementIDProperty, new Binding("SelectedElementID") { Mode = BindingMode.TwoWay });
            BindingOperations.SetBinding(this, ViewportTopLeftProperty, new Binding("CurrentViewportTopLeft") { Mode = BindingMode.TwoWay });
            //BindingOperations.SetBinding(this, ViewportLeftProperty, new Binding("CurrentViewportLeft") { Mode = BindingMode.TwoWay });
            //BindingOperations.SetBinding(this, ViewportTopProperty, new Binding("CurrentViewportTop") { Mode = BindingMode.TwoWay });
        }

        private void OnSecondaryStructureEditorSelectionModeSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            SelectionMode = (StructureElementSelectionMode)selectionModeSelector.SelectedValue;
            secondaryStructureDrawing.SelectionMode = SelectionMode;
        }

        private void OnSecondaryStructureDrawingSizeChanged(object sender, SizeChangedEventArgs e)
        {
            ViewportManager.DrawingSize = new Size(secondaryStructureDrawing.CanvasWidth, secondaryStructureDrawing.CanvasHeight);
            if (ViewModel != null)
            {
                ViewModel.CurrentCanvasSize = ViewportManager.DrawingSize;
                ViewModel.CurrentDrawingSize = new Size(secondaryStructureDrawing.DrawingWidth, secondaryStructureDrawing.DrawingHeight);
                ViewModel.CurrentDrawingOffset = secondaryStructureDrawing.DrawingOffset;
                ViewModel.CurrentDrawingSnapshot = secondaryStructureDrawing.DrawingSnapshot;

                //Kluge
                /*MenuItem printCommandItem = ViewModel.ActionsMenu.Where(mi => mi.Text!=null && mi.Text.Equals("Print")).FirstOrDefault();
                if(printCommandItem==null)
                    ViewModel.ActionsMenu.Add(new MenuItem("Print") { Command = PrintCommand });*/
            }
            HandleZoomFactor();
        }

        private void OnSecondaryStructureEditorSizeChanged(object sender, SizeChangedEventArgs e)
        {
            HandleZoomFactor();
        }

        private void OnViewportManagerViewportLocationChanged(object sender, EventArgs e)
        {
            //ViewportTop = ViewportManager.ViewportTop;
            //ViewportLeft = ViewportManager.ViewportLeft;
            //if (ZoomFactor == 0)
                ViewportTopLeft = ViewportManager.ViewportBounds.TopLeft;
            //else
                //ViewportTopLeft = new Point(ViewportManager.ViewportBounds.TopLeft.X * ZoomFactor, ViewportManager.ViewportBounds.TopLeft.Y * ZoomFactor);
        }

        private void OnViewportManagerViewportSizeChanged(object sender, EventArgs e)
        {
            ViewportSize = new Size(ViewportManager.ViewportBounds.Width, ViewportManager.ViewportBounds.Height);
            if (ViewModel != null)
                ViewModel.CurrentViewportSize = ViewportSize;
        }

        private void OnSecondaryStructureStructureDrawingZoomSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            HandleZoomFactor();
        }

        private void OnSecondaryStructureDrawingSelectedStructureElementVisualIDChanged(object sender, RoutedEventArgs e)
        {
            if (SelectionMode != StructureElementSelectionMode.None)
            {
                ViewModel.SelectedElement = secondaryStructureDrawing.SelectedStructureElementVisual;
                if (ViewModel.SynchronizeViewport)
                {
                    Point selectionLocation = secondaryStructureDrawing.SelectedStructureElementVisualLocation;
                    ViewportManager.CenterViewportAt(selectionLocation);
                }
            }
            else
            {
                ViewModel.SelectedElement = null;
            }
        }

        private void HandleZoomFactor()
        {
            if (zoomSelector.SelectedIndex >= 0 && zoomSelector.SelectedIndex < ZoomFactors.Count)
            {
                ZoomFactorData desiredZoom = ZoomFactors[zoomSelector.SelectedIndex];
                if (desiredZoom.Zoom == 0.0)
                {
                    //We look to see if we can get the canvas size
                    if (drawingPanel == null)
                    {
                        //We don't know the size, default to 100%;
                        ResetZoomFactor();
                    }
                    else
                    {
                        ViewportManager.FitDrawingToScreen(secondaryStructureDrawing.RenderSize);
                        ZoomFactor = ViewportManager.ZoomFactor;
                    }
                }
                else
                {
                    ViewportManager.ZoomFactor = desiredZoom.Zoom;
                    ZoomFactor = ViewportManager.ZoomFactor;
                }
            }
        }

        private void ResetZoomFactor()
        {
            zoomSelector.SelectedIndex = 4;
        }

        private void InitializeSelectionModes()
        {
            SelectionModes = new List<SelectionModeData>();
            foreach (StructureElementSelectionMode elementSelModeVal in Enum.GetValues(typeof(StructureElementSelectionMode))) 
            { 
                SelectionModes.Add(new SelectionModeData() { Mode = elementSelModeVal }); 
            }
            selectionModeSelector.ItemsSource = SelectionModes;
            selectionModeSelector.SelectedIndex = 0; //Default to StructureElementSelectionMode.None
        }

        private void InitializeZoomFactors()
        {
            ZoomFactors = new List<ZoomFactorData>();
            ZoomFactors.Add(new ZoomFactorData { Zoom = 0.0 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 0.25 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 0.50 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 0.75 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 1.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 1.25 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 1.50 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 1.75 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 2.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 3.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 4.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 5.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 6.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 7.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 8.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 9.00 });
            ZoomFactors.Add(new ZoomFactorData { Zoom = 10.00 });
        }

        class ZoomFactorData
        {
            public double Zoom { get; set; }
            public string Label
            {
                get
                {
                    return (Zoom <= 0) ? "Fit To Screen" : string.Format("{0:P}", Zoom);
                }
            }
        }

        class SelectionModeData
        {
            public StructureElementSelectionMode Mode { get; set; }
            public string Label
            {
                get
                {
                    return Mode.ToString();
                }
            }
        }
    }
}
